/*
 * TraceabilityTreeParser.java
 *
 * $Id: TraceabilityTreeParser.java,v 1.3 2009-03-02 15:31:23 mario Exp $
 */
package org.ceteca.explica.core.traceability.tree.parser;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.ceteca.explica.core.traceability.InterfaceTraceabilityErrors;
import org.ceteca.explica.core.traceability.InterfaceTraceabilityTree;
import org.ceteca.explica.core.traceability.tree.ProcessInfo;
import org.ceteca.explica.core.traceability.tree.ProductInfo;
import org.ceteca.explica.core.traceability.tree.PropertyInfo;
import org.ceteca.explica.core.traceability.tree.PurchaseInfo;
import org.ceteca.explica.core.traceability.tree.TraceabilityTree;
import org.ceteca.explica.core.traceability.tree.WarehouseInfo;
import org.ceteca.explica.core.traceability.tree.WorkcenterInfo;
import org.ceteca.explica.core.traceability.tree.WorkerInfo;
import org.ceteca.explica.core.util.LoggerFactory;
import org.ceteca.explica.core.util.ResultMessage;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import org.ceteca.explica.core.traceability.tree.parser.TraceabilityTreeEntityResolver;
import org.ceteca.javaglade.InterfaceGladeParser;

/**
 * This class implements a SAX Parser to parse an XML traceability file.
 * <br/>
 * Responsibilities:
 * <ul>
 *  <li>Parse XML traceability files.</li>
 *  <li>Validate XML traceability files.</li>
 * </ul>
 * <br/>
 * @author Mario García García <mario@imagos.es>
 * Copyright (c) 2007 Fundación Centro Tecnolóxico da Carne
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <br/>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <br/>
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class TraceabilityTreeParser extends DefaultHandler {
	/** Path to the traceability file being parsed */
	private String filename;
	/** Store of the parsed traceability tree */
	private TraceabilityTree trTree;
	/** Temporary store for a product data */
	private ProductInfo trProduct;
	/** Temporary store for a process data */
	private ProcessInfo trProcess;
	/** Temporary store for a purchase data */
	private PurchaseInfo trPurchase;
	/** Temporary store for a warehouse data */
	private WarehouseInfo trWarehouse;
	/** Temporary store for a workcenter data */
	private WorkcenterInfo trWorkcenter;
	/** Temporary store for a worker data */
	private WorkerInfo trWorker;
	/** Temporary store for a property data */
	private PropertyInfo trProperty;
	/** Temporary store for an appcc data */
	private String trAppcc;
	/** Stack of products to build the correct product's hierarchy */
	private Stack<ProductInfo> sProducts;
	/** Stack of processes to build the correct process's hierarchy */
	private Stack<ProcessInfo> sProcesses;
	/** Stack of comments to build the correct comment's hierarchy */
	private Stack<ArrayList<String>> sComments;
	/** Number of in-parse state products */
	private int productDepth;
	/** Number of in-parse state processes */
	private int processDepth;
	/** Temporary store to keep the content of XML elements of type PCDATA */
	private String content;
	/** Temporary store for the list of comments for the current in-parse
	 * element.
	 */
	private ArrayList<String> lComments;
	
	/**
     * Parser constructor. Creates a new instance of the parser and parses
     * the file specified. 
     * @param aPathFile String, path of the traceability (xml) file that will 
     * be parsed.
     * @throws ParserConfigurationException if a parser cannot be created which 
     * satisfies the requested configuration. 
     * @throws SAXException If there is a problem processing the xml file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     * @throws IOException in case the file doesn't exist or cannot be opened/read.
     */
    public TraceabilityTreeParser(String aPathFile) throws ParserConfigurationException, 
                                                 IOException, SAXException {
    	
        try {
            // XML parsing using SAX Parser.
            SAXParserFactory tmpFactory = SAXParserFactory.newInstance();
            tmpFactory.setValidating(true);
//            tmpFactory.setValidating(false);

//            SAXParser parser = tmpFactory.newSAXParser();
            
            // Create the SAX parser instance.
            SAXParser parser = tmpFactory.newSAXParser();
            // Get the XMLReader instance embedded into the SAX Parser. This
            // XMLReader instance will allow the configuration of parsing parameters.
            XMLReader xmlReader = parser.getXMLReader();
            
            // Set the EntityResolver instance to redirect external URIs to secure
            // local versions (especially DTD).
            xmlReader.setEntityResolver(new TraceabilityTreeEntityResolver());
            xmlReader.setContentHandler(this);
            xmlReader.setErrorHandler(this);
            
            
            // Open the file to check if the file exists
            File f = new File(aPathFile);
            if (f == null || !f.exists())
            	throw new IOException("The file: " + aPathFile + " doesn't exist or cannot be opened!");
            
            LoggerFactory.getLogger(InterfaceGladeParser.JAVAGLADE_LOG_CATEGORY).
				info(String.format(InterfaceGladeParser.JAVAGLADE_LOG_PARSE_GLADE, f.getPath()));
            
            InputStream fis = new FileInputStream(f);
            LoggerFactory.getLogger(InterfaceGladeParser.JAVAGLADE_LOG_CATEGORY).
            	info(String.format(InterfaceGladeParser.JAVAGLADE_LOG_PARSE_GLADE, fis));
            
            InputSource source = new InputSource(fis);
            LoggerFactory.getLogger(InterfaceGladeParser.JAVAGLADE_LOG_CATEGORY).
        		info(String.format(InterfaceGladeParser.JAVAGLADE_LOG_PARSE_GLADE, source));
            
            xmlReader.parse(source);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } catch (SAXException e) {
        	e.printStackTrace();
            throw e;
        }
    }
    
    /**
     * Parser constructor. Creates a new instance of the parser and parses
     * the file specified. 
     * @param is InputStream, input stream of the traceability (xml) file that 
     * will be parsed.
     * @throws ParserConfigurationException if a parser cannot be created which 
     * satisfies the requested configuration. 
     * @throws SAXException If there is a problem processing the xml file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     * @throws IOException in case the file doesn't exist or cannot be opened/read.
     */
    public TraceabilityTreeParser(InputStream is) throws ParserConfigurationException, 
                                                 IOException, SAXException {
    	
        try {
        	// XML parsing using SAX Parser.
            SAXParserFactory tmpFactory = SAXParserFactory.newInstance();
            tmpFactory.setValidating(true);
//            tmpFactory.setValidating(false);

//            SAXParser parser = tmpFactory.newSAXParser();
            
            // Create the SAX parser instance.
            SAXParser parser = tmpFactory.newSAXParser();
            // Get the XMLReader instance embedded into the SAX Parser. This
            // XMLReader instance will allow the configuration of parsing parameters.
            XMLReader xmlReader = parser.getXMLReader();
            
            // Set the EntityResolver instance to redirect external URIs to secure
            // local versions (especially DTD).
            xmlReader.setEntityResolver(new TraceabilityTreeEntityResolver());
            xmlReader.setContentHandler(this);
            xmlReader.setErrorHandler(this);
            
            
            InputSource source = new InputSource(is);
            LoggerFactory.getLogger(InterfaceGladeParser.JAVAGLADE_LOG_CATEGORY).
        		info(String.format(InterfaceGladeParser.JAVAGLADE_LOG_PARSE_GLADE, source));
            
            xmlReader.parse(source);
        } catch (ParserConfigurationException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (IOException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (SAXException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        }
    }
    
    /**
     * Parser constructor. Creates a new instance of the parser and parses
     * the content specified. 
     * @param content byte[], content of the traceability (xml) file that 
     * will be parsed.
     * @throws ParserConfigurationException if a parser cannot be created which 
     * satisfies the requested configuration. 
     * @throws SAXException If there is a problem processing the xml file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     * @throws IOException in case the file doesn't exist or cannot be opened/read.
     */
    public TraceabilityTreeParser(byte[] content) throws ParserConfigurationException, 
                                                 IOException, SAXException {
    	
        try {
        	// XML parsing using SAX Parser.
            SAXParserFactory tmpFactory = SAXParserFactory.newInstance();
            tmpFactory.setValidating(true);
//            tmpFactory.setValidating(false);

//            SAXParser parser = tmpFactory.newSAXParser();
            
            // Create the SAX parser instance.
            SAXParser parser = tmpFactory.newSAXParser();
            // Get the XMLReader instance embedded into the SAX Parser. This
            // XMLReader instance will allow the configuration of parsing parameters.
            XMLReader xmlReader = parser.getXMLReader();
            
            // Set the EntityResolver instance to redirect external URIs to secure
            // local versions (especially DTD).
            xmlReader.setEntityResolver(new TraceabilityTreeEntityResolver());
            xmlReader.setContentHandler(this);
            xmlReader.setErrorHandler(this);
            
            
            // Open the file to check if the file exists
            ByteArrayInputStream bis = new ByteArrayInputStream(content);
            
            InputSource source = new InputSource(bis);
            LoggerFactory.getLogger(InterfaceGladeParser.JAVAGLADE_LOG_CATEGORY).
        		info(String.format(InterfaceGladeParser.JAVAGLADE_LOG_PARSE_GLADE, source));
            
            xmlReader.parse(source);
        } catch (ParserConfigurationException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (IOException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (SAXException e) {
        	LoggerFactory.getLogger(InterfaceTraceabilityTree.TREEVIEW_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        }
    }

    /**
     * Gets the parsed traceability file path.
     * @return String, traceability file path.
     */
    public String getFilename() {
		return filename;
	}
    /**
	 * Sets up parsed XML traceability file path.
	 * @param filename String, traceability file path.
	 */
	public void setFilename(String filename) {
		this.filename = filename;
	}

	/**
	 * Gets the number of products being parsed at the time.
	 * @return int, number of products being parsed.
	 */
	public int getProductDepth() {
		return productDepth;
	}
	/**
	 * Sets up the number of products being parsed at the time.
	 * @param productDepth int, number of products being parsed.
	 */
	public void setProductDepth(int productDepth) {
		this.productDepth = productDepth;
	}
	/**
	 * Gets the stack of products being parsed at the time.
	 * @return Stack<ProductInfo>, stack of products being parsed.
	 */
	public Stack<ProductInfo> getSProducts() {
		return sProducts;
	}
	/**
	 * Sets up the stack of products being parsed at the time.
	 * @param products Stack<ProductInfo>, stack of products being parsed.
	 */
	public void setSProducts(Stack<ProductInfo> products) {
		sProducts = products;
	}
	
	/**
	 * Gets the number of processes being parsed at the time.
	 * @return int, number of processes being parsed.
	 */
	public int getProcessDepth() {
		return processDepth;
	}
	/**
	 * Sets up the number of processes being parsed at the time.
	 * @param processDepth int, number of processes being parsed.
	 */
	public void setProcessDepth(int processDepth) {
		this.processDepth = processDepth;
	}
	/**
	 * Gets the stack of processes being parsed at the time.
	 * @return Stack<ProcessInfo>, stack of processes being parsed.
	 */
	public Stack<ProcessInfo> getSProcesses() {
		return sProcesses;
	}
	/**
	 * Sets up the stack of processes being parsed at the time.
	 * @param processes Stack<ProcessInfo>, stack of processes being parsed.
	 */
	public void setSProcesses(Stack<ProcessInfo> processes) {
		sProcesses = processes;
	}

	/**
	 * Gets the stack of comments being parsed at the time.
	 * @return Stack<String>, stack of comments being parsed.
	 */
	public Stack<ArrayList<String>> getSComments() {
		return sComments;
	}
	/**
	 * Sets up the stack of comments being parsed at the time.
	 * @param comments Stack<String>, stack of comments being parsed.
	 */
	public void setSComments(Stack<ArrayList<String>> comments) {
		sComments = comments;
	}
	
	/**
	 * Gets the temporary appcc data
	 * @return String, temporary appcc data.
	 */
	public String getTrAppcc() {
		return trAppcc;
	}
	/**
	 * Sets up temporary appcc data.
	 * @param trAppcc String, appcc data.
	 */
	public void setTrAppcc(String trAppcc) {
		this.trAppcc = trAppcc;
	}
	
	/**
	 * Gets the temporary process data
	 * @return ProcessInfo, temporary process data.
	 */
	public ProcessInfo getTrProcess() {
		return trProcess;
	}
	/**
	 * Sets up temporary process data.
	 * @param trProcess ProcessInfo, process data.
	 */
	public void setTrProcess(ProcessInfo trProcess) {
		this.trProcess = trProcess;
	}

	/**
	 * Gets the temporary product data
	 * @return ProductInfo, temporary product data.
	 */
	public ProductInfo getTrProduct() {
		return trProduct;
	}
	/**
	 * Sets up temporary product data.
	 * @param trProduct ProductInfo, product data.
	 */
	public void setTrProduct(ProductInfo trProduct) {
		this.trProduct = trProduct;
	}

	/**
	 * Gets the temporary purchase data
	 * @return PurchaseInfo, temporary purchase data.
	 */
	public PurchaseInfo getTrPurchase() {
		return trPurchase;
	}
	/**
	 * Sets up temporary purchase data.
	 * @param trPurchase PurchaseInfo, purchase data.
	 */
	public void setTrPurchase(PurchaseInfo trPurchase) {
		this.trPurchase = trPurchase;
	}

	/**
	 * Gets the temporary traceability tree data
	 * @return TraceabilityTree, temporary traceability tree data.
	 */
	public TraceabilityTree getTrTree() {
		return trTree;
	}
	/**
	 * Sets up temporary traceability tree data.
	 * @param trTree TraceabilityTree, traceability tree data.
	 */
	public void setTrTree(TraceabilityTree trTree) {
		this.trTree = trTree;
	}

	/**
	 * Gets the temporary warehouse data
	 * @return WarehouseInfo, temporary warehouse data.
	 */
	public WarehouseInfo getTrWarehouse() {
		return trWarehouse;
	}
	/**
	 * Sets up temporary warehouse data.
	 * @param trWarehouse WarehouseInfo, warehouse data.
	 */
	public void setTrWarehouse(WarehouseInfo trWarehouse) {
		this.trWarehouse = trWarehouse;
	}

	/**
	 * Gets the temporary workcenter data
	 * @return WorkcenterInfo, temporary workcenter data.
	 */
	public WorkcenterInfo getTrWorkcenter() {
		return trWorkcenter;
	}
	/**
	 * Sets up temporary workcenter data.
	 * @param trWorkcenter WorkcenterInfo, workcenter data.
	 */
	public void setTrWorkcenter(WorkcenterInfo trWorkcenter) {
		this.trWorkcenter = trWorkcenter;
	}
	
	/**
	 * Gets the temporary worker data
	 * @return WorkerInfo, temporary worker data.
	 */
	public WorkerInfo getTrWorker() {
		return trWorker;
	}
	/**
	 * Sets up temporary worker data.
	 * @param trWorker WorkerInfo, worker data.
	 */
	public void setTrWorker(WorkerInfo trWorker) {
		this.trWorker = trWorker;
	}
	
	/**
	 * Gets the temporary property data.
	 * @return PropertyInfo, temporary property data.
	 */
	public PropertyInfo getTrProperty() {
		return trProperty;
	}
	/**
	 * Sets up temporary property data.
	 * @param trProperty PropertyInfo, property data.
	 */
	public void setTrProperty(PropertyInfo trProperty) {
		this.trProperty = trProperty;
	}

	/**
	 * Gets the PCDATA XML element content.
	 * @return String, parsed content.
	 */
	public String getContent() {
		return content;
	}
	/**
	 * Sets up the PCDATA XML element content.
	 * @param content String, parsed content.
	 */
	public void setContent(String content) {
		this.content = content;
	}
	
	/**
	 * Updates the content of a PCDATA XML element with the parsed characters.
	 * @param ch char[], set of characters that is being parsed.
	 * @param start int, starting index of the first character parsed.
	 * @param length int, number of characters parsed.
	 */
	public void appendContent(char[] ch, int start, int length) {
		String str = new String(ch, start, length);
		content += str;
	}

	/**
	 * Gets the list of comments being parsed.
	 * @return ArrayList<String>, List of comments.
	 */
	private ArrayList<String> getLComments() {
		return this.lComments;
	}
	/**
	 * Sets up the list of comments being parsed.
	 * @param lComments ArrayList<String>, list of comments.
	 */
	private void setLComments(ArrayList<String> lComments) {
		this.lComments = lComments;
	}

	/**
	 * Receive notification of the start of the traceability file to parse.
     * @throws SAXException, if there is a problem processing the traceability
     * file (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
	 */
	
	public void startDocument() throws SAXException {
		System.out.println("startDocument");
	    this.setTrTree(null);
	    this.setTrAppcc(null);
	    this.setTrProcess(null);
	    this.setTrProduct(null);
	    this.setTrPurchase(null);
	    this.setTrWarehouse(null);
	    this.setTrWorkcenter(null);
	    this.setTrWorker(null);
	    this.setTrProperty(null);
	    this.setSProducts(new Stack<ProductInfo>());
	    this.setProductDepth(0);
	    this.setSProcesses(new Stack<ProcessInfo>());
	    this.setProcessDepth(0);
	    this.setSComments(new Stack<ArrayList<String>>());
	    this.setLComments(null);
	    
	    // Reset the content of the tag
		this.setContent("");
	}
	
    /**
     * Receive notification of the start of an element.
     * @param uri String, 
     * @param localName String, the local name (without prefix), or the empty string 
     * if Namespace processing is not being performed. 
     * @param qName String, the qualified name (with prefix), or the empty string 
     * if qualified names are not available. 
     * @param attributes Attributes, the specified or defaulted attributes. 
     * @throws SAXException, if there is a problem processing the traceability file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
    public void startElement(String uri, String localName, String qName, Attributes attributes) 
    			throws SAXException {

/*    	System.out.println("startElement: '" + uri + "' '" + localName + "' '" + qName +
				"' '" +	attributes + "'");
		for ( int i=0; i < attributes.getLength(); i++ ) {
			System.out.println("\tattributes[" + i + "]=" + attributes.getLocalName(i) + ":" + attributes.getValue(i));
		}
*/
    	
    	// If the tag name is null or empty
    	if (qName == null || qName.length() <= 0) {
    		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_EMPTY_TAG);
    		throw new SAXException(res.getMessage());
    	}
    	// If starting <traceability-tree> tag
    	else if (qName.equals(InterfaceTraceabilityTree.TRACEABILITY_TREE_TAG)) {
	    	// Create a new instance of GladeInterface
	        this.trTree = new TraceabilityTree(attributes.getValue(InterfaceTraceabilityTree.PROPERTY_TRACEABILITY_TREE_XMLNS_PROPERTYNAME));
	    }
    	// If starting <product> tag
    	else if (qName.equals(InterfaceTraceabilityTree.PRODUCT_TAG)) {
    		// Add the previous product to the products' stack
        	if (this.trProduct != null)
        		this.sProducts.push(this.trProduct);
        	
        	// Create a new instance of ProductInfo
        	this.trProduct = new ProductInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_REFERENCE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_SETNUMBER_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_BARCODE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_DESCRIPTION_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_DATEIN_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_DATEEXPIRY_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PRODUCT_TRACEABILITYFILE_PROPERTYNAME)
    		);
        	
        	// Increment the product depth
        	this.incProductDepth();
        	
        	// Add the previous list of comments to the comments' stack
        	if (this.lComments != null)
        		this.sComments.push(this.lComments);

        	// Create a new instance of Comments list
        	this.lComments = new ArrayList<String>();
    	}
    	// If starting a <process> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROCESS_TAG)) {
    		// Add the previous process to the processes' stack
        	if (this.trProcess != null)
        		this.sProcesses.push(this.trProcess);
        	
        	// Create a new instance of ProcessInfo
        	this.trProcess = new ProcessInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROCESS_DATEPROCESS_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROCESS_PROCESSCODE_PROPERTYNAME)
    		);
        	
        	// Increment the product depth
        	this.incProcessDepth();
        	
        	// Add the previous list of comments to the comments' stack
        	if (this.lComments != null)
        		this.sComments.push(this.lComments);

        	// Create a new instance of Comments list
        	this.lComments = new ArrayList<String>();        }
    	// If starting a <processIn> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROCESS_IN_TAG)) {
        }
        // If starting a <purchase> tag
        else if (qName.equals(InterfaceTraceabilityTree.PURCHASE_TAG)) {
        	// Create a new instance of PurchaseInfo
    		this.trPurchase = new PurchaseInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PURCHASE_PURCHASECONFIRMATION_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PURCHASE_SUPPLIER_PROPERTYNAME)
    		);
        }
    	// If starting a <property> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROPERTY_TAG)) {
        	// Create a new instance of PropertyInfo
    		this.trProperty = new PropertyInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROPERTY_CODE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROPERTY_VALUE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROPERTY_TYPE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROPERTY_DESCRIPTION_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_PROPERTY_PATTERN_PROPERTYNAME)
    		);
        }
    	// If starting a <source> tag
        else if (qName.equals(InterfaceTraceabilityTree.SOURCE_TAG)) {
        }
    	// If starting a <worker> tag
        else if (qName.equals(InterfaceTraceabilityTree.WORKER_TAG)) {
        	// Create a new instance of WorkerInfo
    		this.trWorker = new WorkerInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WORKER_CODE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WORKER_NAME_PROPERTYNAME)
    		);
        }
    	// If starting a <workcenter> tag
        else if (qName.equals(InterfaceTraceabilityTree.WORKCENTER_TAG)) {
        	// Create a new instance of WorkcenterInfo
    		this.trWorkcenter = new WorkcenterInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WORKCENTER_CODE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WORKCENTER_DESCRIPTION_PROPERTYNAME)
    		);
        }
    	// If starting a <warehouse> tag
        else if (qName.equals(InterfaceTraceabilityTree.WAREHOUSE_TAG)) {
        	// Create a new instance of WarehouseInfo
    		this.trWarehouse = new WarehouseInfo(
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WAREHOUSE_CODE_PROPERTYNAME),
    				attributes.getValue(InterfaceTraceabilityTree.PROPERTY_WAREHOUSE_DESCRIPTION_PROPERTYNAME)
    		);
        }
    	// If starting a <comment> tag
        else if (qName.equals(InterfaceTraceabilityTree.COMMENT_TAG)) {
        }
    	// If starting a <appcc> tag
        else if (qName.equals(InterfaceTraceabilityTree.APPCC_TAG)) {
        }
    	// else if starting any other tag --> Error 
    	else {
    		ArrayList<String> lParams = new ArrayList<String>();
    		lParams.add(qName);
    		lParams.add(InterfaceTraceabilityTree.TRACEABILITY_TREE_TAG);
    		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_UNEXPECTED_TAG, lParams);
    		throw new SAXException(res.getMessage());
    	}
    	
    	// Reset the content of the tag
    	this.setContent("");
    }

    /**
     * Receive notification of the end of an element.
     * @param uri String, 
     * @param localName String, the local name (without prefix), or the empty string 
     * if Namespace processing is not being performed. 
     * @param qName String, the qualified name (with prefix), or the empty string 
     * if qualified names are not available. 
     * @throws SAXException, if there is a problem processing the glade file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
    public void endElement(String uri, String localName, 
                           String qName) throws SAXException {

    	// If the tag name is null or empty
    	if (qName == null || qName.length() <= 0) {
    		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_EMPTY_TAG);
    		throw new SAXException(res.getMessage());
    	}
    	// If ending <traceability-tree> tag
    	else if (qName.equals(InterfaceTraceabilityTree.TRACEABILITY_TREE_TAG)) {
	    }
    	// If ending <product> tag
    	else if (qName.equals(InterfaceTraceabilityTree.PRODUCT_TAG)) {
    		// Decrement the depth of the products
        	this.decProductDepth();

        	// Add the current list of comments to the product's comments list
        	if (this.lComments != null)
        		this.trProduct.addComments(this.lComments);
        	
        	// Pop the last list of comments from the stack
        	try {
        		this.lComments = this.sComments.pop();
        	}
        	catch (EmptyStackException ex) {
        		this.lComments = null;
        	}
        	
    		// If the product is not the root node of the traceability tree -->
    		// It must the in of a process so add it to the current process
        	if (this.getProductDepth() > 0)
        		this.trProcess.addIn(this.trProduct);
    		// The product the traceability tree is built for --> Set the product
    		// in the traceability tree
        	else
        		this.trTree.setProduct(this.trProduct);
        	
        	// Pop the last product from the products' stack
        	try {
        		this.trProduct = this.sProducts.pop();
        	}
        	catch (EmptyStackException ex) {
        		this.trProduct = null;
        	}
    	}
    	// If ending a <process> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROCESS_TAG)) {
        	// Decrement the depth of the processes
        	this.decProcessDepth();
        	
        	// Add the current list of comments to the process's comments list
        	if (this.lComments != null)
        		this.trProcess.addComments(this.lComments);
        	
        	// Pop the last list of comments from the stack
        	try {
        		this.lComments = this.sComments.pop();
        	}
        	catch (EmptyStackException ex) {
        		this.lComments = null;
        	}

        	// Set the process as the source of the current product
        	this.trProduct.setSource(this.trProcess);

        	// Pop the last process from the processes' stack
        	try {
        		this.trProcess = this.sProcesses.pop();
        	}
        	catch (EmptyStackException ex) {
        		this.trProcess = null;
        	}
        }
        // If ending a <source> tag
        else if (qName.equals(InterfaceTraceabilityTree.SOURCE_TAG)) {
        }
    	// If ending a <purchase> tag
        else if (qName.equals(InterfaceTraceabilityTree.PURCHASE_TAG)) {
        	// Set the purchase as the current product's source
       		this.trProduct.setSource(this.trPurchase);
        }
    	// If ending a <property> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROPERTY_TAG)) {
        	// Add the property to the product's property list
       		this.trProduct.addProperty(this.trProperty);
        }
    	// If ending a <processIn> tag
        else if (qName.equals(InterfaceTraceabilityTree.PROCESS_IN_TAG)) {
        }
    	// If ending a <worker> tag
        else if (qName.equals(InterfaceTraceabilityTree.WORKER_TAG)) {
        	// Add the worker to the current process's list of workers
       		this.trProcess.addWorker(this.trWorker);
        }
    	// If ending a <workcenter> tag
        else if (qName.equals(InterfaceTraceabilityTree.WORKCENTER_TAG)) {
        	// Add the workcenter to the current process's list of workcenters
       		this.trProcess.addWorkcenter(this.trWorkcenter);
        }
    	// If ending a <warehouse> tag
        else if (qName.equals(InterfaceTraceabilityTree.WAREHOUSE_TAG)) {
        	// Add the warehouse to the current product
        	this.trProduct.addWarehouse(this.trWarehouse);
        }
    	// If ending a <comment> tag
        else if (qName.equals(InterfaceTraceabilityTree.COMMENT_TAG)) {
        	this.lComments.add(this.getContent());
        }
    	// If ending a <appcc> tag
        else if (qName.equals(InterfaceTraceabilityTree.APPCC_TAG)) {
        	// Set the value of the appcc (the one that is stored as 
        	// the content of the appcc tag)
        	this.trAppcc = new String(this.getContent());
        	// Add the comment to the comments list of the right element (<product> or <process>)
   			this.trWarehouse.addAppcc(this.trAppcc);
        }
    	// else if ending any other tag --> Error 
    	else {
    		ArrayList<String> lParams = new ArrayList<String>();
    		lParams.add(qName);
    		lParams.add(InterfaceTraceabilityTree.TRACEABILITY_TREE_TAG);
    		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_UNEXPECTED_TAG, lParams);
    		throw new SAXException(res.getMessage());
    	}
    }

    /**
     * Receive notification of character data inside an element.
     * @param ch char[], The characters. 
     * @param start int, The start position in the character array.
     * @param length int, The number of characters to use from the character array. 
     * @throws SAXException, if there is a problem processing the glade file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
	public void characters(char[] ch, int start, int length) throws SAXException {
		this.appendContent(ch, start, length);
	}
	
	/**
	 * Receive notification of a parser warning.
	 * @param e SAXParseException, The warning information encoded as an exception. 
	 */
	public void warning(SAXParseException e) throws SAXException {
		e.printStackTrace();
		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_GENERAL_ERROR, this.getErrorParams(e));
		System.err.println(res.getMessage());
    }
	
	/**
	 * Receive notification of a recoverable parser error.
	 * @param e SAXParseException, The warning information encoded as an exception.
	 */
    public void error(SAXParseException e) throws SAXException {
    	e.printStackTrace();
    	ResultMessage res2 = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_GENERAL_ERROR, this.getErrorParams(e));
    	System.err.println(res2.getMessage());
    	// Ignore the identifier not found SAXException
    	if (e.getMessage().contains("An element with the identifier") &&
    		e.getMessage().contains("must appear in the document.")) {
    		// Ignore the exception
    	} // if exception == Identifier not found
    	else {
	    	ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_GENERAL_ERROR, this.getErrorParams(e));
			throw new SAXException(res.getMessage());
    	} // else
    }
    
    /**
     * Report a fatal XML parsing error.
     * @param e SAXParseException, The warning information encoded as an exception.
     */
    public void fatalError(SAXParseException e) throws SAXException {
    	e.printStackTrace();
		ResultMessage res = new ResultMessage(InterfaceTraceabilityErrors.ERR_TRACEABILITY_PARSING_GENERAL_ERROR, this.getErrorParams(e));
		throw new SAXException(res.getMessage());
    }
    
    /**
     * Gets the parameters of an Exception for printing the formatted
     * error message.
     * @param e SAXParseException, The warning information encoded as an exception.
     */
    private ArrayList<String> getErrorParams(SAXParseException e) {
    	ArrayList<String> lParams = new ArrayList<String>();
    	lParams.add(e.getPublicId());
    	lParams.add(e.getSystemId());
    	lParams.add("" + e.getLineNumber());
    	lParams.add("" + e.getColumnNumber());
    	lParams.add(e.getMessage());
     	return lParams;
    }
    
    /**
     * Increases the number of products being parsed.
     */
    private void incProductDepth() {
    	this.productDepth++;
    }
    /**
     * Decreases the number of products being parsed.
     */
    private void decProductDepth() {
    	this.productDepth--;
    }
    
    /**
     * Increases the number of processes being parsed.
     */
    private void incProcessDepth() {
    	this.processDepth++;
    }
    /**
     * Decreases the number of processes being parsed.
     */
    private void decProcessDepth() {
    	this.processDepth--;
    }
    
    /**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: TraceabilityTreeParser.java,v 1.3 2009-03-02 15:31:23 mario Exp $";
	}
}